Add neurorift package skeleton, skill system, runtime & model checks, and secure command runner#40
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (82)
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| class SkillLoader: | ||
| def run(self, skill_dir: Path, **kwargs): | ||
| meta=json.loads((skill_dir/'skill.json').read_text(encoding='utf-8')) | ||
| entry=skill_dir/meta['entrypoint'] |
There was a problem hiding this comment.
Code Injection (🔒 Security, 🔴 Critical) - The code dynamically loads and runs a module based on external input without safe checks, risking execution of harmful code from untrusted sources. View in Corgea ↗
More Details
🎟️Issue Explanation: The code dynamically loads and runs a module based on external input without safe checks, risking execution of harmful code from untrusted sources.
- "entry=skill_dir/meta['entrypoint']" uses external input to locate code, which attackers can manipulate to load malicious modules.
- "spec.loader.exec_module(mod)" runs the loaded module's code, allowing injected code to execute and compromise the system.
- If "meta['entrypoint']" points to harmful code, this bypasses safeguards and runs unauthorized actions or data leaks.
🪄Fix Explanation: The fix validates the 'entrypoint' from skill metadata to ensure it contains only safe characters and forbids path traversal or special symbols, preventing arbitrary code execution by restricting dynamic imports to known module names.
- The entrypoint string is sanitized by checking for emptiness, unwanted characters like "'.'", "'/'", "'\\'", and ensuring it contains only alphanumeric characters and underscores via "entry_name.replace('_','').isalnum()".
- Instead of importing from a file path, the code now builds a trusted module name string "neurorift.skills.{entry_name}" and uses "importlib.util.find_spec()" to locate it, avoiding risky path-based imports.
- The fix validates the module spec existence and loader presence, returning an error if the module is unknown, thus preventing attempts to load unauthorized code.
- Executing the module and calling "run()" proceeds only after these stringent checks, stopping injection via manipulated entrypoints.
💡Important Instructions: Ensure the skill modules are correctly installed or discoverable under the
neurorift.skills namespace package for find_spec() to work properly.
| entry=skill_dir/meta['entrypoint'] | |
| entry_name=str(meta.get('entrypoint','')).strip() | |
| if (not entry_name) or ('.' in entry_name) or ('/' in entry_name) or ('\\' in entry_name) or (not entry_name.replace('_','').isalnum()): | |
| return {'error':'invalid entrypoint'} | |
| module_name=f"neurorift.skills.{entry_name}" | |
| spec=importlib.util.find_spec(module_name) | |
| if spec is None or spec.loader is None: | |
| return {'error':'unknown skill'} | |
| mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) |
| return self.installer.install(pkg) | ||
| def list(self): return self.installer.registry.list() | ||
| def run(self, skill_name: str, **kwargs): | ||
| return self.loader.run(self.installer.installed / skill_name, **kwargs) |
There was a problem hiding this comment.
Path Traversal (🔒 Security, 🔴 High) - The code may allow attackers to trick the program into accessing files outside a safe folder by manipulating file paths using the "skill_name" input. View in Corgea ↗
More Details
🎟️Issue Explanation: The code may allow attackers to trick the program into accessing files outside a safe folder by manipulating file paths using the "skill_name" input.
- The input "skill_name" is used to build file paths without cleaning special parts like "../" that go up folders.
- Attackers can input something like "../secret" to break out of the restricted folder and uninstall or access unintended files.
- Since the method handles skill uninstallation, improper path handling risks affecting files outside safe skill directories, which is sensitive.
🪄Fix Explanation: The fix validates the skill_name input to prevent path traversal by rejecting absolute paths, parent directory references, and multi-part paths. It resolves and verifies the target path is within the allowed directory before proceeding.
- The fix converts "skill_name" to a "Path" object and rejects absolute paths or those containing "'..'" components to block path traversal.
- It ensures "skill_name" has only one path part ("len(p.parts) != 1") preventing nested paths.
- The base installation directory is resolved with "self.installer.installed.resolve()" and the target path is resolved similarly.
- The code checks if "target.relative_to(base)" succeeds to confirm the resolved target is within the allowed installation directory.
- If any validation fails, it raises "ValueError("Invalid skill_name")", stopping unsafe path usage in "run" and "uninstall".
💡Important Instructions: Ensure all other usage of skill_name for file access is similarly protected or constrained; no further immediate changes needed in this code snippet.
| return self.loader.run(self.installer.installed / skill_name, **kwargs) | |
| p = Path(skill_name) | |
| if p.is_absolute() or any(part == '..' for part in p.parts) or len(p.parts) != 1: | |
| raise ValueError("Invalid skill_name") | |
| base = self.installer.installed.resolve() | |
| target = (base / p.name).resolve() | |
| try: | |
| target.relative_to(base) | |
| except ValueError: | |
| raise ValueError("Invalid skill_name") | |
| return self.loader.run(target, **kwargs) | |
| def uninstall(self, skill_name: str): | |
| p = Path(skill_name) | |
| if p.is_absolute() or any(part == '..' for part in p.parts) or len(p.parts) != 1: | |
| raise ValueError("Invalid skill_name") | |
| base = self.installer.installed.resolve() | |
| target = (base / p.name).resolve() | |
| try: | |
| target.relative_to(base) | |
| except ValueError: | |
| raise ValueError("Invalid skill_name") | |
| return self.installer.uninstall(p.name) |
| pkg=self.client.fetch_skill(skill_name, self.examples) | ||
| return self.installer.install(pkg) | ||
| def list(self): return self.installer.registry.list() | ||
| def run(self, skill_name: str, **kwargs): |
There was a problem hiding this comment.
Code Injection (🔒 Security, 🔴 Critical) - The code builds a path using user input "skill_name" without checks, risking unintended behavior when special characters are used. View in Corgea ↗
More Details
🎟️Issue Explanation: The code builds a path using user input "skill_name" without checks, risking unintended behavior when special characters are used.
- "skill_name" is used directly to form a filepath: "self.installer.installed / skill_name" which can be manipulated.
- An attacker could input "../" sequences, causing directory traversal to access files outside expected paths.
- This can let unauthorized code run or leak sensitive info, as the path controls which code "self.loader.run()" executes.
🪄Fix Explanation: The fix validates the skill name against an allowlist and ensures no path traversal by resolving paths and checking directory containment, preventing arbitrary code execution via crafted inputs.
- Retrieves the canonical installed directory path using "installed_dir = self.installer.installed.resolve()" to avoid symbolic link tricks.
- Fetches a set of valid skill names from "self.installer.registry.list()" to enforce an allowlist, raising if the skill is unknown.
- Resolves the full skill path with "skill_path = (installed_dir / skill_name).resolve()" to normalize and expand any path components.
- Checks that the resolved skill path is a child of the installed directory with "if installed_dir not in skill_path.parents", detecting and blocking path traversal attempts.
- Only after validation, it calls "self.loader.run(skill_path, **kwargs)", ensuring safe and intended skill execution.
💡Important Instructions: Ensure that the registry listing is always accurate and reflects current installed skills to avoid blocking valid inputs or allowing revoked skills.
| def run(self, skill_name: str, **kwargs): | |
| def run(self, skill_name: str, **kwargs): | |
| # Validate skill name using allowlist of installed skills and prevent path traversal | |
| installed_dir = self.installer.installed.resolve() | |
| try: | |
| installed_names = set(self.installer.registry.list()) | |
| except Exception: | |
| installed_names = None | |
| if installed_names is not None and skill_name not in installed_names: | |
| raise ValueError(f"Unknown or uninstalled skill: {skill_name}") | |
| skill_path = (installed_dir / skill_name).resolve() | |
| if installed_dir not in skill_path.parents: | |
| raise ValueError("Invalid skill name or path traversal detected") | |
| return self.loader.run(skill_path, **kwargs) |
Sequence DiagramThis PR introduces a new command routing path where the CLI can either execute skill lifecycle actions or start autonomous agent mode. The run agent path is now gated by runtime environment and model capability checks before the agent runtime is launched. sequenceDiagram
participant User
participant CLI
participant SkillManager
participant RuntimeCheck
participant ModelCheck
participant AgentRuntime
User->>CLI: Run neurorift command
alt Skill operation
CLI->>SkillManager: Install list run or uninstall skill
SkillManager-->>CLI: Skill result
CLI-->>User: Return skill output
else Run agent
CLI->>RuntimeCheck: Verify tools and work directory
RuntimeCheck-->>CLI: Environment ready
CLI->>ModelCheck: Verify model capabilities
ModelCheck-->>CLI: Agent ready
CLI->>AgentRuntime: Start autonomous run agent mode
AgentRuntime-->>User: Agent session started
end
Generated by CodeAnt AI |
| return self.root / f"{sid}.json" | ||
|
|
||
| def save(self, session: SessionContext): | ||
| self.path(session.session_id).write_text( | ||
| json.dumps(session.__dict__, default=str), encoding="utf-8" | ||
| ) | ||
|
|
||
| def load(self, sid: str): | ||
| p = self.path(sid) | ||
| return ( | ||
| SessionContext(**json.loads(p.read_text(encoding="utf-8"))) | ||
| if p.exists() |
There was a problem hiding this comment.
Path Traversal (🔒 Security, 🔴 High) - This code builds a file path using external input "sid" without checking it, risking access outside the allowed folder, which can expose or modify unintended files. View in Corgea ↗
More Details
🎟️Issue Explanation: This code builds a file path using external input "sid" without checking it, risking access outside the allowed folder, which can expose or modify unintended files.
- The code uses "self.root / f"{sid}.json"" to create paths, but "sid" may include "../" to move outside the restricted directory.
- Attackers can supply input like "../../etc/passwd" to read or change files outside the intended folder, leading to data leaks or damage.
- Since the function deals with file paths tied to sensitive data (".json"), unrestricted input can compromise security or system integrity.
🪄Fix Explanation: The fix sanitizes the session ID by rejecting absolute paths, separator-containing names, and traversal tokens, then ensures the resolved file path lies within the intended root directory, effectively preventing path traversal attacks.
The code resolves "self.root" to an absolute path for consistent comparison using "root = self.root.resolve(strict=True)".
It converts the session ID to a "Path" object and rejects invalid IDs that are absolute, contain multiple parts, or equal "." or "..", blocking path traversal attempts.
The candidate file path is constructed with "(root / f"{candidate_name.name}.json")" and resolved without requiring existence.
It verifies the resolved candidate path is within the root directory by confirming "root in candidate.parents", raising an error otherwise.
This combination prevents an attacker from escaping the restricted directory, closing the vulnerability.
💡Important Instructions: Ensure that all session ID inputs at the API boundary are validated before reaching this method to avoid unexpected exceptions.
| return self.root / f"{sid}.json" | |
| def save(self, session: SessionContext): | |
| self.path(session.session_id).write_text( | |
| json.dumps(session.__dict__, default=str), encoding="utf-8" | |
| ) | |
| def load(self, sid: str): | |
| p = self.path(sid) | |
| return ( | |
| SessionContext(**json.loads(p.read_text(encoding="utf-8"))) | |
| if p.exists() | |
| # Sanitize session ID to prevent path traversal and enforce containment within the store root | |
| root = self.root.resolve(strict=True) | |
| candidate_name = Path(sid) | |
| # Reject absolute paths and any path containing separators or traversal components | |
| if candidate_name.is_absolute() or len(candidate_name.parts) != 1 or candidate_name.name in (".", ".."): | |
| raise ValueError("invalid session id") | |
| candidate = (root / f"{candidate_name.name}.json").resolve(strict=False) | |
| # Verify the path is within the root directory after resolution | |
| if root not in candidate.parents: | |
| raise ValueError("invalid session id") | |
| return candidate |
|
|
||
| class SkillLoader: | ||
| def run(self, skill_dir: Path, **kwargs): | ||
| meta = json.loads((skill_dir / "skill.json").read_text(encoding="utf-8")) |
There was a problem hiding this comment.
Code Injection (🔒 Security, 🔴 Critical) - The code loads and runs a Python module based on external input without sanitizing it, risking malicious code execution inside the system. View in Corgea ↗
More Details
🎟️Issue Explanation: The code loads and runs a Python module based on external input without sanitizing it, risking malicious code execution inside the system.
- The "entrypoint" in "meta" is from an external file "skill.json", letting attackers specify any file to run.
- Using "exec_module" runs code in the specified file, so if attacker controls "entrypoint", they can execute arbitrary code.
- This is risky because the loaded module’s "run()" function has full access to the program’s environment and sensitive data.
🪄Fix Explanation: The fix restricts module loading to a fixed filename within the skill directory and validates the path to prevent path traversal or symlink bypass, ensuring only trusted code is executed.
"entry = (skill_dir / "skill.py").resolve()" forces a fixed, safe entrypoint filename to eliminate arbitrary file loading risks.
The code verifies "entry" is inside "skill_dir", is a regular file, and not a symlink to block path traversal and symlink attacks.
Safe module name creation uses sanitized directory name via "re.sub(r"[^0-9A-Za-z_]", "_", skill_dir.name)" to prevent malformed or malicious module names.
Module spec and loader presence are checked explicitly, returning errors for invalid specs to avoid undefined behavior.
Loading errors during "spec.loader.exec_module(mod)" are caught to prevent crashes, safely handling malformed modules or runtime errors at import.
💡Important Instructions: Ensure all skill packages follow the fixed entrypoint convention by containing a "skill.py" with a callable run() function; otherwise, skill loading will fail.
diff --git a/neurorift/skills/loader.py b/neurorift/skills/loader.py
index ddb9a53..0c2b65c 100644
--- a/neurorift/skills/loader.py
+++ b/neurorift/skills/loader.py
@@ -1,3 +1,4 @@
+import importlib.util, json, re
import importlib.util, json
from pathlib import Path
@@ -5,8 +6,28 @@ from pathlib import Path
class SkillLoader:
def run(self, skill_dir: Path, **kwargs):
meta = json.loads((skill_dir / "skill.json").read_text(encoding="utf-8"))
- entry = skill_dir / meta["entrypoint"]
- spec = importlib.util.spec_from_file_location(f"skill_{meta['name']}", entry)
+ # Only allow a fixed, safe entrypoint name to avoid path traversal and arbitrary imports
+ entry = (skill_dir / "skill.py").resolve()
+ base_dir = skill_dir.resolve()
+ if not isinstance(meta, dict):
+ return {"error": "invalid metadata"}
+ # Ensure the resolved entrypoint is within the skill directory and is a regular file (no symlinks)
+ try:
+ if entry.is_symlink() or not entry.is_file() or base_dir not in entry.parents:
+ return {"error": "invalid entrypoint"}
+ except OSError:
+ return {"error": "invalid entrypoint"}
+ # Build a safe module name derived from the directory name, not untrusted metadata
+ safe_mod_name = "skill_" + re.sub(r"[^0-9A-Za-z_]", "_", skill_dir.name)
+ spec = importlib.util.spec_from_file_location(safe_mod_name, str(entry))
+ if spec is None or spec.loader is None:
+ return {"error": "invalid module spec"}
mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod)
- return mod.run(**kwargs) if hasattr(mod, "run") else {"error": "missing run()"}
+ try:
+ spec.loader.exec_module(mod)
+ except Exception:
+ return {"error": "failed to load skill"}
+ run_fn = getattr(mod, "run", None)
+ if not callable(run_fn):
+ return {"error": "missing run()"}
+ return run_fn(**kwargs)
To apply the fix, Download .patch.
| meta = json.loads((pkg / "skill.json").read_text(encoding="utf-8")) | ||
| name = meta["name"] | ||
| dst = self.installed / name | ||
| if dst.exists(): | ||
| shutil.rmtree(dst) | ||
| shutil.copytree(pkg, dst) | ||
| self.registry.add(name) | ||
| return {"success": True, "name": name, "path": str(dst)} | ||
|
|
||
| def uninstall(self, name: str): | ||
| dst = self.installed / name | ||
| if dst.exists(): | ||
| shutil.rmtree(dst) | ||
| self.registry.remove(name) | ||
| return {"success": True, "name": name} |
There was a problem hiding this comment.
Path Traversal (🔒 Security, 🔴 Critical) - The code uses external input to build a directory path but doesn't properly check if the path is inside a safe folder, risking unauthorized access or overwriting files outside it. View in Corgea ↗
More Details
🎟️Issue Explanation: The code uses external input to build a directory path but doesn't properly check if the path is inside a safe folder, risking unauthorized access or overwriting files outside it.
- The code sets "dst = self.installed / name", where "name" comes from external JSON data without validation.
- If "name" contains path tricks like "../", it can escape the "self.installed" folder and overwrite important files.
- Example: "name = "../../etc"" leads "shutil.rmtree(dst)" to delete system files outside the intended safe directory.
🪄Fix Explanation: The fix prevents path traversal by validating skill names against a strict pattern and ensuring resolved paths are within the intended installed directory, stopping attackers from escaping to unauthorized locations.
- Added regex validation "re.fullmatch(r"[A-Za-z0-9._-]+", name)" to allow only safe characters in skill names, preventing malicious input.
- Used "Path.resolve()" on both the installed directory and target path to get absolute paths, preventing traversal via symbolic links or ../ segments.
- Verified that the resolved target path is a subpath of the resolved installed directory using "dst.relative_to(installed_root)" to detect and reject traversal attempts.
- Applied the same validation both when installing and uninstalling to ensure consistent protections on all file operations regarding skill names.
- On validation failure, the code returns clear error messages like ""invalid skill name"" or ""path traversal detected"", safely aborting unsafe operations.
💡Important Instructions: Ensure that any other components calling these install/uninstall methods also handle the error responses correctly and avoid proceeding on failure. No further code changes are required.
diff --git a/neurorift/skills/installer.py b/neurorift/skills/installer.py
index 5899d99..44a5ad3 100644
--- a/neurorift/skills/installer.py
+++ b/neurorift/skills/installer.py
@@ -1,3 +1,4 @@
+import json, shutil, re
import json, shutil
from pathlib import Path
from neurorift.skills.skill_validator import SkillValidator
@@ -19,8 +20,15 @@ class SkillInstaller:
if not ok:
return {"success": False, "error": f"missing:{missing}"}
meta = json.loads((pkg / "skill.json").read_text(encoding="utf-8"))
- name = meta["name"]
- dst = self.installed / name
+ name = str(meta.get("name", "")).strip()
+ if not name or not re.fullmatch(r"[A-Za-z0-9._-]+", name):
+ return {"success": False, "error": "invalid skill name"}
+ installed_root = self.installed.resolve()
+ dst = (installed_root / name).resolve()
+ try:
+ dst.relative_to(installed_root)
+ except Exception:
+ return {"success": False, "error": "path traversal detected"}
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(pkg, dst)
@@ -28,7 +36,14 @@ class SkillInstaller:
return {"success": True, "name": name, "path": str(dst)}
def uninstall(self, name: str):
- dst = self.installed / name
+ if not name or not isinstance(name, str) or not re.fullmatch(r"[A-Za-z0-9._-]+", name):
+ return {"success": False, "error": "invalid skill name"}
+ installed_root = self.installed.resolve()
+ dst = (installed_root / name).resolve()
+ try:
+ dst.relative_to(installed_root)
+ except Exception:
+ return {"success": False, "error": "path traversal detected"}
if dst.exists():
shutil.rmtree(dst)
self.registry.remove(name)
To apply the fix, Download .patch.
| return {"success": True, "name": name, "path": str(dst)} | ||
|
|
||
| def uninstall(self, name: str): | ||
| dst = self.installed / name |
There was a problem hiding this comment.
Path Traversal (🔒 Security, 🔴 Critical) - The code lets users delete files by name but doesn't stop them from using special path parts to delete files outside the allowed folder, risking unwanted file removal. View in Corgea ↗
More Details
🎟️Issue Explanation: The code lets users delete files by name but doesn't stop them from using special path parts to delete files outside the allowed folder, risking unwanted file removal.
- "dst = self.installed / name" builds a path from user input "name" without sanitizing it.
- Special elements like "../" in "name" let attackers delete files outside the restricted directory.
- An attacker can pass "../../important_folder" as "name" to remove sensitive files outside "self.installed".
🪄Fix Explanation: The fix prevents directory traversal by validating and resolving the input path before use, ensuring the target directory is always within the allowed installation folder.
- Uses "Path(name).is_absolute()" and checks for forbidden segments ("..", "", ".") to reject absolute paths and path traversal attempts.
- Resolves the base directory with "self.installed.resolve()" to get a canonical path for comparison.
- Combines and resolves the target path "dst = (base_dir / name).resolve()" to prevent symlink or traversal exploits.
- Verifies that "dst" is a subdirectory of "base_dir" using "dst.relative_to(base_dir)" to ensure containment.
- Only removes "dst" if it passes validation and exists, avoiding accidental deletion outside the intended folder.
💡Important Instructions: Ensure
self.installed is correctly set and absolute before this method is called to maintain consistent base directory resolution.
| dst = self.installed / name | |
| base_dir = self.installed.resolve() | |
| # Reject absolute paths and traversal sequences in 'name' | |
| if Path(name).is_absolute() or any(part in ("..", "", ".") for part in Path(name).parts): | |
| return {"success": False, "error": "invalid name"} | |
| dst = (base_dir / name).resolve() | |
| try: | |
| dst.relative_to(base_dir) | |
| except ValueError: | |
| return {"success": False, "error": "invalid name"} | |
| if dst.exists(): | |
| shutil.rmtree(dst) |
|
|
Overall Grade Focus Area: Complexity |
Security Reliability Complexity Hygiene |
Feedback
- Copy-pasted scaffolding left untrimmed
- Unused imports, arguments, reimports and shadowed names stem from copying templates and ad‑hoc edits; remove dead symbols, avoid reimport/shadowing, and consolidate repeated scaffolding into small helpers to stop repetition-driven clutter.
- Misapplied object orientation
- Many methods never touch instance state; convert them to staticmethods or module-level functions to clarify intent, reduce surface area, and avoid accidental coupling to mutable object state.
- Fragile filesystem and subprocess handling
- StartsWith path checks, insecure temp usage, and partial executable paths allow traversal and PATH hijacks; resolve and compare absolute paths, use secure tempfile context managers, and invoke executables via absolute paths or shutil.which.
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| Python | Mar 14, 2026 12:55p.m. | Review ↗ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7dc4d1659f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not stdout.strip() and not stderr.strip(): | ||
| validation_passed = False | ||
| error_type = error_type or "empty_output" |
There was a problem hiding this comment.
Accept zero-output subprocesses as successful
_validate_tool_output marks any run with both stdout and stderr empty as invalid, and _run_command_secure requires that validation to pass even when returncode == 0. This causes legitimately successful silent commands to be reported as failures (for example installers/probes that intentionally emit no output), which can make tool installation and command execution fail incorrectly.
Useful? React with 👍 / 👎.
|
|
||
| def main() -> int: | ||
| parser = build_parser() | ||
| ns = parser.parse_args() |
There was a problem hiding this comment.
Preserve delegated CLI flags when parsing wrapper args
The new wrapper calls parse_args() before delegating to neurorift_main, and argparse exits on unrecognized options, so existing top-level NeuroRift flags (for example neurorift --target ... or neurorift --configure) are rejected by the wrapper instead of being forwarded. This is a regression from the prior direct entrypoint and breaks normal CLI usage unless users manually insert --.
Useful? React with 👍 / 👎.
| self.client = ClawHubClient(self.installer.cache) | ||
| self.examples = Path(__file__).resolve().parents[1] / 'skill_store' / 'installed' | ||
| def install_clawhub(self, skill_name: str): | ||
| pkg=self.client.fetch_skill(skill_name, self.examples) |
There was a problem hiding this comment.
Return structured error when requested skill is missing
install_clawhub directly propagates FileNotFoundError from fetch_skill for unknown skill names, but the CLI caller expects a result dictionary and does not catch exceptions. Running --clawhub <missing-skill> therefore crashes with a traceback instead of returning the intended {success: false, error: ...} response path.
Useful? React with 👍 / 👎.
Nitpicks 🔍
|
| start = raw_text.find("{") | ||
| end = raw_text.rfind("}") | ||
| if start == -1 or end == -1 or end <= start: | ||
| raise ValueError("No JSON object found in model response") | ||
| return json.loads(raw_text[start : end + 1]) |
There was a problem hiding this comment.
Suggestion: JSON extraction is brittle because it slices from the first { to the last } in the entire response. If the model adds explanatory text containing braces before or after the actual JSON object, parsing fails even when a valid JSON object is present. Decode from each { position until a valid JSON object is found. [logic error]
Severity Level: Major ⚠️
- ⚠️ run-agent startup can fail despite valid capability JSON.
- ⚠️ Model readiness check becomes fragile to response formatting.| start = raw_text.find("{") | |
| end = raw_text.rfind("}") | |
| if start == -1 or end == -1 or end <= start: | |
| raise ValueError("No JSON object found in model response") | |
| return json.loads(raw_text[start : end + 1]) | |
| decoder = json.JSONDecoder() | |
| for idx, char in enumerate(raw_text): | |
| if char != "{": | |
| continue | |
| try: | |
| parsed, _ = decoder.raw_decode(raw_text[idx:]) | |
| except json.JSONDecodeError: | |
| continue | |
| if isinstance(parsed, dict): | |
| return parsed | |
| raise ValueError("No JSON object found in model response") |
Steps of Reproduction ✅
1. Run `neurorift run-agent --model <model>`; CLI forwards to `neurorift_main.main()` via
`neurorift_cli.py:50-53`.
2. `run-agent` branch in `neurorift_main.py:1008-1020` calls `verify_model_capabilities()`
(`model_capability_check.py:35`).
3. If Ollama output contains valid JSON plus extra brace text, `_extract_json()`
(`model_capability_check.py:26-32`) slices first `{` to last `}`, creating invalid JSON.
4. `verify_model_capabilities()` catches parse failure at
`model_capability_check.py:57-64`, returns `invalid_capability_json`, and
`neurorift_main.py:1020-1023` blocks agent startup.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** model_capability_check.py
**Line:** 28:32
**Comment:**
*Logic Error: JSON extraction is brittle because it slices from the first `{` to the last `}` in the entire response. If the model adds explanatory text containing braces before or after the actual JSON object, parsing fails even when a valid JSON object is present. Decode from each `{` position until a valid JSON object is found.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| required = { | ||
| "tool_usage", | ||
| "command_generation", | ||
| "filesystem_operations", | ||
| "multi_step_reasoning", | ||
| "agent_ready", | ||
| } | ||
| if not required.issubset(parsed): | ||
| return { | ||
| "ok": False, | ||
| "error": "capability_fields_missing", | ||
| "parsed": parsed, | ||
| "agent_ready": False, | ||
| } | ||
|
|
||
| parsed["ok"] = bool(parsed.get("agent_ready")) | ||
| return parsed |
There was a problem hiding this comment.
Suggestion: Capability fields are only checked for presence, not type, so a response like "agent_ready": "false" is treated as truthy by callers and can incorrectly pass readiness checks. Validate that all required fields are booleans and compute ok from the normalized boolean value. [type error]
Severity Level: Critical 🚨
- ❌ Non-agent-ready models can bypass autonomous safety gate.
- ⚠️ run-agent reliability degrades with loosely formatted model JSON.| required = { | |
| "tool_usage", | |
| "command_generation", | |
| "filesystem_operations", | |
| "multi_step_reasoning", | |
| "agent_ready", | |
| } | |
| if not required.issubset(parsed): | |
| return { | |
| "ok": False, | |
| "error": "capability_fields_missing", | |
| "parsed": parsed, | |
| "agent_ready": False, | |
| } | |
| parsed["ok"] = bool(parsed.get("agent_ready")) | |
| return parsed | |
| required = { | |
| "tool_usage", | |
| "command_generation", | |
| "filesystem_operations", | |
| "multi_step_reasoning", | |
| "agent_ready", | |
| } | |
| if not isinstance(parsed, dict) or not required.issubset(parsed): | |
| return { | |
| "ok": False, | |
| "error": "capability_fields_missing", | |
| "parsed": parsed, | |
| "agent_ready": False, | |
| } | |
| for field in required: | |
| if not isinstance(parsed.get(field), bool): | |
| return { | |
| "ok": False, | |
| "error": f"capability_field_not_boolean:{field}", | |
| "parsed": parsed, | |
| "agent_ready": False, | |
| } | |
| parsed["ok"] = parsed["agent_ready"] | |
| return parsed |
Steps of Reproduction ✅
1. Invoke `neurorift run-agent --model <model>`; parser defines this command in
`neurorift_main.py:938-942`.
2. Execution reaches capability gate in `neurorift_main.py:1019-1023`, which checks `if
not capability.get("agent_ready")`.
3. If model returns JSON like `"agent_ready": "false"`, current code
(`model_capability_check.py:67-83`) accepts field presence only and keeps string value.
4. Non-empty string is truthy, so `neurorift_main.py:1020` treats model as ready and
continues autonomous run incorrectly.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** model_capability_check.py
**Line:** 67:83
**Comment:**
*Type Error: Capability fields are only checked for presence, not type, so a response like `"agent_ready": "false"` is treated as truthy by callers and can incorrectly pass readiness checks. Validate that all required fields are booleans and compute `ok` from the normalized boolean value.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| @@ -0,0 +1,5 @@ | |||
| class Channel: | |||
| def connect(self): return True | |||
| def receive_message(self, payload=None): return payload or {} | |||
There was a problem hiding this comment.
Suggestion: The message receiver currently uses truthiness (payload or {}), which silently converts valid falsy payloads like 0, "", False, or [] into {}. This corrupts incoming data and breaks callers that rely on those values. Only default to {} when the payload is actually None. [logic error]
Severity Level: Major ⚠️
- ⚠️ Web channel payload fidelity breaks for falsy values.
- ⚠️ Downstream handlers can receive wrong message type.
- ⚠️ Skeleton channel API behavior becomes inconsistent.| def receive_message(self, payload=None): return payload or {} | |
| def receive_message(self, payload=None): return {} if payload is None else payload |
Steps of Reproduction ✅
1. Inspect `neurorift/channels/web_channel.py:3`; `Channel.receive_message()` returns
`payload or {}`, so Python truthiness is applied.
2. Verify current call graph: Grep across `"/workspace/NeuroRift"` finds no
callers/imports of `neurorift.channels.web_channel.Channel` (only channel class
definitions), so this module is currently scaffold code.
3. Reproduce via the public class API directly: in Python, run `from
neurorift.channels.web_channel import Channel; Channel().receive_message("")`.
4. Observe return value is `{}` instead of `""` (same for `0`, `False`, `[]`), confirming
valid falsy payloads are corrupted at `web_channel.py:3`.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/channels/web_channel.py
**Line:** 3:3
**Comment:**
*Logic Error: The message receiver currently uses truthiness (`payload or {}`), which silently converts valid falsy payloads like `0`, `""`, `False`, or `[]` into `{}`. This corrupts incoming data and breaks callers that rely on those values. Only default to `{}` when the payload is actually `None`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| class ClawHubClient: | ||
| def __init__(self, cache_dir: Path): self.cache_dir=cache_dir; cache_dir.mkdir(parents=True, exist_ok=True) | ||
| def fetch_skill(self, skill_name: str, source_dir: Path) -> Path: | ||
| src = source_dir / skill_name |
There was a problem hiding this comment.
Suggestion: skill_name is used directly in path joins, so values like ../... or absolute paths can escape source_dir/cache_dir, allowing unintended directory deletion/copy outside the skill store. Validate and normalize the name before building paths. [security]
Severity Level: Critical 🚨
- ❌ `--clawhub` can access paths outside skill store.
- ❌ Arbitrary directory removal possible via destination override.
- ⚠️ Skill installation trust boundary is bypassed.| src = source_dir / skill_name | |
| skill_path = Path(skill_name) | |
| if skill_path.is_absolute() or ".." in skill_path.parts or skill_path.name != skill_name: | |
| raise ValueError("Invalid skill name") | |
| src = source_dir / skill_path |
Steps of Reproduction ✅
1. Run CLI with ClawHub install path: `neurorift --clawhub /tmp` (entry argument defined
at `neurorift_main.py:915` and handled at `neurorift_main.py:983-984`).
2. `SkillManager.install_clawhub()` forwards raw user input unchanged at
`neurorift/skills/skill_manager.py:13-14`.
3. `ClawHubClient.fetch_skill()` joins unsanitized `skill_name` directly into paths at
`neurorift/clawhub/clawhub_client.py:6` and `:8`; absolute input overrides
`source_dir`/`cache_dir`.
4. Existing destination is deleted by `shutil.rmtree(dst)` at
`neurorift/clawhub/clawhub_client.py:9`, so unintended filesystem paths can be
removed/copied outside intended skill store.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/clawhub/clawhub_client.py
**Line:** 6:6
**Comment:**
*Security: `skill_name` is used directly in path joins, so values like `../...` or absolute paths can escape `source_dir`/`cache_dir`, allowing unintended directory deletion/copy outside the skill store. Validate and normalize the name before building paths.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| def __init__(self, cache_dir: Path): self.cache_dir=cache_dir; cache_dir.mkdir(parents=True, exist_ok=True) | ||
| def fetch_skill(self, skill_name: str, source_dir: Path) -> Path: | ||
| src = source_dir / skill_name | ||
| if not src.exists(): raise FileNotFoundError(skill_name) |
There was a problem hiding this comment.
Suggestion: Checking only exists() allows regular files to pass, but shutil.copytree requires a directory and will raise at runtime. Require the source skill path to be a directory before copying. [possible bug]
Severity Level: Major ⚠️
- ❌ `--clawhub` install crashes on file-name input.
- ⚠️ No graceful install error response returned.
- ⚠️ User sees stacktrace instead of actionable message.| if not src.exists(): raise FileNotFoundError(skill_name) | |
| if not src.is_dir(): | |
| raise FileNotFoundError(skill_name) |
Steps of Reproduction ✅
1. Execute `neurorift --clawhub __init__.py` (CLI path handled at
`neurorift_main.py:983-984`).
2. `SkillManager.install_clawhub()` passes this value to `fetch_skill()` at
`neurorift/skills/skill_manager.py:14`.
3. In `fetch_skill()`, `src` points to `neurorift/skill_store/installed/__init__.py` (file
confirmed in repository), and `src.exists()` passes at
`neurorift/clawhub/clawhub_client.py:7`.
4. `shutil.copytree(src, dst)` at `neurorift/clawhub/clawhub_client.py:10` raises runtime
error because source is not a directory; no local try/except exists in the `args.clawhub`
branch (`neurorift_main.py:983-990`).Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/clawhub/clawhub_client.py
**Line:** 7:7
**Comment:**
*Possible Bug: Checking only `exists()` allows regular files to pass, but `shutil.copytree` requires a directory and will raise at runtime. Require the source skill path to be a directory before copying.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| class SkillLoader: | ||
| def run(self, skill_dir: Path, **kwargs): | ||
| meta=json.loads((skill_dir/'skill.json').read_text(encoding='utf-8')) | ||
| entry=skill_dir/meta['entrypoint'] |
There was a problem hiding this comment.
Suggestion: The entrypoint path from skill.json is used directly, so absolute paths or .. segments can escape the skill directory and load arbitrary files. Resolve the path and enforce that it stays inside the installed skill folder before importing. [security]
Severity Level: Critical 🚨
- ❌ `--skill run` can execute arbitrary host Python files.
- ⚠️ Skill sandbox boundary in `skills/installed` is bypassed.
- ⚠️ Malicious skill metadata gains broader filesystem code execution.| entry=skill_dir/meta['entrypoint'] | |
| entry = (skill_dir / meta['entrypoint']).resolve() | |
| try: | |
| entry.relative_to(skill_dir.resolve()) | |
| except ValueError: | |
| return {'error': 'invalid entrypoint path'} |
Steps of Reproduction ✅
1. Use the real CLI run path in `neurorift_main.py:991-999` (`--skill run <name>`), which
calls `SkillManager.run()` at `neurorift/skills/skill_manager.py:17-18`.
2. Install a skill (or reuse `recon_scanner`), then edit
`~/.neurorift/skills/installed/recon_scanner/skill.json` so `entrypoint` is absolute
(`/tmp/payload.py`) or traversal (`../payload.py`).
3. Run `neurorift --skill run recon_scanner`; execution reaches `SkillLoader.run()` at
`neurorift/skills/loader.py:6-8`.
4. Observe `entry=skill_dir/meta['entrypoint']` (`loader.py:6`) allows escape, then
`spec.loader.exec_module(mod)` (`loader.py:8`) executes that external file.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/skills/loader.py
**Line:** 6:6
**Comment:**
*Security: The entrypoint path from `skill.json` is used directly, so absolute paths or `..` segments can escape the skill directory and load arbitrary files. Resolve the path and enforce that it stays inside the installed skill folder before importing.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| spec=importlib.util.spec_from_file_location(f"skill_{meta['name']}", entry) | ||
| mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) |
There was a problem hiding this comment.
Suggestion: The module import path assumes spec and spec.loader are always valid and that execution will succeed, but invalid entrypoint files can make this fail with uncaught exceptions. Validate the spec/loader and catch load errors to prevent CLI termination. [possible bug]
Severity Level: Major ⚠️
- ❌ Broken skill entrypoints crash `--skill run` execution.
- ⚠️ No graceful error for invalid import specification.
- ⚠️ Skill runtime reliability degrades with malformed packages.| spec=importlib.util.spec_from_file_location(f"skill_{meta['name']}", entry) | |
| mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) | |
| spec = importlib.util.spec_from_file_location(f"skill_{meta['name']}", entry) | |
| if spec is None or spec.loader is None: | |
| return {'error': 'invalid skill entrypoint'} | |
| try: | |
| mod = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(mod) | |
| except OSError as exc: | |
| return {'error': f'failed to load skill module: {exc}'} |
Steps of Reproduction ✅
1. Use normal run flow (`neurorift_main.py:996-999`) for any installed skill.
2. Corrupt that skill's metadata/file state: set `entrypoint` in `skill.json` to a bad
file, or remove the target file under `~/.neurorift/skills/installed/<name>/`.
3. Execution reaches `SkillLoader.run()` at `neurorift/skills/loader.py:7-8`, where
`spec`/`loader` are assumed valid and `exec_module` is unguarded.
4. Invalid module load raises uncaught import/file exceptions, which bubble to
`neurorift_main.py:1522` (`asyncio.run`) and terminate the CLI command.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/skills/loader.py
**Line:** 7:8
**Comment:**
*Possible Bug: The module import path assumes `spec` and `spec.loader` are always valid and that execution will succeed, but invalid entrypoint files can make this fail with uncaught exceptions. Validate the spec/loader and catch load errors to prevent CLI termination.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| def __init__(self, root: Path): | ||
| self.path = root / 'registry.json'; root.mkdir(parents=True, exist_ok=True) | ||
| if not self.path.exists(): self.path.write_text(json.dumps({'installed_skills': []}, indent=2), encoding='utf-8') | ||
| def read(self): return json.loads(self.path.read_text(encoding='utf-8')) |
There was a problem hiding this comment.
Suggestion: Reading the registry assumes valid JSON and will crash with JSONDecodeError if the file is partially written or corrupted. Handle decode failures and return a safe default structure so list/add/remove do not fail at startup. [possible bug]
Severity Level: Critical 🚨
- ❌ `--skill list` crashes on malformed registry.
- ❌ Install/uninstall paths fail when reading registry.
- ⚠️ Manual registry repair required to recover.| def read(self): return json.loads(self.path.read_text(encoding='utf-8')) | |
| def read(self): | |
| try: | |
| return json.loads(self.path.read_text(encoding='utf-8')) | |
| except json.JSONDecodeError: | |
| return {'installed_skills': []} |
Steps of Reproduction ✅
1. Invoke official CLI path (`setup.py:52` console script -> `neurorift_cli.py:52` ->
`neurorift_main.main()` at `neurorift_main.py:1473-1522`) using `--skill list`.
2. Ensure `~/.neurorift/skills/registry.json` contains invalid JSON (e.g., truncated
content), then run `neurorift --skill list`.
3. Execution reaches `skill_manager.list()` (`neurorift_main.py:994`,
`neurorift/skills/skill_manager.py:16`) -> `registry.list()` (`registry.py:8`) ->
`registry.read()` (`registry.py:7`).
4. `json.loads(...)` raises uncaught `JSONDecodeError`, and skill commands terminate
instead of recovering.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/skills/registry.py
**Line:** 7:7
**Comment:**
*Possible Bug: Reading the registry assumes valid JSON and will crash with JSONDecodeError if the file is partially written or corrupted. Handle decode failures and return a safe default structure so list/add/remove do not fail at startup.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| def add(self, name): | ||
| data=self.read(); skills=data.setdefault('installed_skills', []) | ||
| if name not in skills: skills.append(name) | ||
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') |
There was a problem hiding this comment.
Suggestion: The add operation performs a read-modify-write without synchronization, so concurrent installs can overwrite each other and lose skill entries. Acquire an exclusive file lock around the whole update to make the operation atomic across processes. [race condition]
Severity Level: Major ⚠️
- ⚠️ Concurrent installs can lose registry entries.
- ❌ Installed skill list becomes inconsistent.
- ⚠️ Subsequent uninstall targets wrong registry state.| def add(self, name): | |
| data=self.read(); skills=data.setdefault('installed_skills', []) | |
| if name not in skills: skills.append(name) | |
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') | |
| def add(self, name): | |
| lock_path = self.path.with_suffix(".lock") | |
| with lock_path.open("w", encoding="utf-8") as lock_file: | |
| import fcntl | |
| fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) | |
| data = self.read() | |
| skills = data.setdefault('installed_skills', []) | |
| if name not in skills: | |
| skills.append(name) | |
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') |
Steps of Reproduction ✅
1. Use the shipped call chain that reaches `SkillRegistry.add`:
`neurorift_main._async_main()` creates `SkillManager` (`neurorift_main.py:981`),
`SkillManager.install_clawhub()` calls `SkillInstaller.install()`
(`neurorift/skills/skill_manager.py:13-15`), and `install()` calls
`self.registry.add(name)` (`neurorift/skills/installer.py:18`).
2. Start two processes targeting the same registry file
(`~/.neurorift/skills/registry.json`, path created by
`SkillManager(Path.home()/".neurorift")` at `neurorift_main.py:981`), each invoking
`SkillRegistry.add()` with different names.
3. Both processes execute unsynchronized read-modify-write in
`neurorift/skills/registry.py:10-12` (read snapshot, append, overwrite file).
4. Observe one skill missing afterward when listing registry contents (`registry.py:8`),
showing a lost update from concurrent writes.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/skills/registry.py
**Line:** 9:12
**Comment:**
*Race Condition: The add operation performs a read-modify-write without synchronization, so concurrent installs can overwrite each other and lose skill entries. Acquire an exclusive file lock around the whole update to make the operation atomic across processes.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| def remove(self, name): | ||
| data=self.read(); data['installed_skills']=[s for s in data.get('installed_skills',[]) if s!=name] | ||
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') |
There was a problem hiding this comment.
Suggestion: The remove operation also does unsynchronized read-modify-write, so uninstalling while another process is adding/removing can revert newer changes. Lock the registry update to prevent lost writes. [race condition]
Severity Level: Major ⚠️
- ⚠️ Concurrent uninstalls can revert each other.
- ❌ Removed skills may reappear in registry.
- ⚠️ Skill lifecycle state becomes unreliable.| def remove(self, name): | |
| data=self.read(); data['installed_skills']=[s for s in data.get('installed_skills',[]) if s!=name] | |
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') | |
| def remove(self, name): | |
| lock_path = self.path.with_suffix(".lock") | |
| with lock_path.open("w", encoding="utf-8") as lock_file: | |
| import fcntl | |
| fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) | |
| data = self.read() | |
| data['installed_skills'] = [s for s in data.get('installed_skills', []) if s != name] | |
| self.path.write_text(json.dumps(data, indent=2), encoding='utf-8') |
Steps of Reproduction ✅
1. Follow actual uninstall path: `neurorift_main.py:1002-1004` (`--skill uninstall`) calls
`SkillManager.uninstall()` (`neurorift/skills/skill_manager.py:19`), which calls
`SkillInstaller.uninstall()` (`neurorift/skills/installer.py:20-24`), which calls
`registry.remove(name)` (`installer.py:23`).
2. Prepare registry with two skills, then launch two processes against the same file, both
calling `SkillRegistry.remove()` for different names.
3. Each process reads old data and writes independently via
`neurorift/skills/registry.py:14-15` without lock coordination.
4. Final file may retain one supposedly removed skill (last-writer-wins), proving
concurrent remove/update rollback behavior.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/skills/registry.py
**Line:** 13:15
**Comment:**
*Race Condition: The remove operation also does unsynchronized read-modify-write, so uninstalling while another process is adding/removing can revert newer changes. Lock the registry update to prevent lost writes.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.|
CodeAnt AI finished reviewing your PR. |
User description
Motivation
neuroriftpackage layout and CLI entrypoint to support a pluggable multi-agent architecture and skill marketplace.Description
neuroriftpackage with many modules (channels, core, execution, gateway, memory, models, sessions, skills, storage, tools, utils) providing lightweight implementations and stubs for agent orchestration and session handling.SkillInstaller,SkillLoader,SkillRegistry,SkillManager, and example skillrecon_scannerunderskill_store/installedwith CLI hooks for--clawhuband--skilloperations.runtime_environment_check.pyto validate required binaries and workdir, andmodel_capability_check.pyto probe Ollama models for agent readiness and parse JSON capability responses.neurorift_main.pywith a secure subprocess runner (_run_command_secure) and multiple safety helpers (_get_restricted_env,_build_preexec_limits,_sanitize_output,_validate_tool_output,_should_retry), improved tool detection/installation logic, skill management integration, and newrun-agentcommand flow; addedneurorift_cli.pyas the package CLI andneurorift_launch.shhelper script.setup.pyto include the new package and set the console entry point toneurorift_cli:main.Testing
Codex Task
CodeAnt-AI Description
Add NeuroRift package, safe startup checks, secure command runner, and skill management
What Changed
Impact
✅ Clearer model readiness errors✅ Fewer startup failures from missing/unusable tools✅ Safer tool execution and fewer command-related crashes💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.